/**
 * Project: ameba.biz
 * 
 * File Created at 2015年12月29日
 * $Id$
 * 
 * Copyright 2015 dface.cn Croporation Limited.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * dface Company. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with www.dface.cn
 */
package cn.dface.ameba.biz.service.impl;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.joda.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.dface.ameba.biz.bean.AppDeployLog;
import cn.dface.ameba.biz.service.ApplicationService;
import cn.dface.ameba.biz.vo.AppDeployProcess;
import cn.dface.ameba.biz.vo.AppDeployRecord;
import cn.dface.ameba.biz.vo.ApplicationApiVo;
import cn.dface.ameba.biz.vo.ApplicationTagVo;
import cn.dface.ameba.biz.vo.ApplicationVersionVo;
import cn.dface.ameba.dal.dao.ApplicationApiDAO;
import cn.dface.ameba.dal.dao.ApplicationTagDAO;
import cn.dface.ameba.dal.dao.ApplicationVersionDAO;
import cn.dface.ameba.dal.entity.ApplicationApiDo;
import cn.dface.ameba.dal.entity.ApplicationTagDo;
import cn.dface.ameba.dal.entity.ApplicationVersionDo;
import cn.dface.ameba.dal.entity.EnumApiUsage;
import cn.dface.ameba.dal.entity.EnumAppUsage;
import cn.dface.ameba.dal.entity.EnumVersionStatus;
import cn.dface.framework.core.enums.ResultEnums;
import cn.dface.framework.core.log.DfaceLog;
import cn.dface.framework.core.page.PageModel;
import cn.dface.framework.core.vo.ResultVo;

/**
 * ApplicationServiceImpl
 * 
 * @author LVZY
 */
@Service
public class ApplicationServiceImpl implements ApplicationService {
	private DfaceLog dfaceLog = DfaceLog.getLogger(ApplicationServiceImpl.class);

    @Autowired
    ApplicationTagDAO     appTagDao;
    @Autowired
    ApplicationVersionDAO appVersionDao;
    @Autowired
    ApplicationApiDAO     appApiDao;

    @Autowired
    AppDeployLog          appDeployLog;

    @Override
    public ApplicationTagVo saveApplicationTag(ApplicationTagVo appTag) {
        ApplicationTagVo retVo;
        if (null == appTag.getSid()) {
            ApplicationTagDo entity = new ApplicationTagDo();
            entity.setName(appTag.getName());
            entity.setDescription(appTag.getDescription());
            entity.setAppUsage(EnumAppUsage.valueOf(appTag.getUsage()));
            this.appTagDao.create(entity);
            appTag.setSid(entity.getSid());
            retVo = appTag;
        } else {
            ApplicationTagDo entity = this.appTagDao.retriveById(appTag.getSid());
            if (null == entity) {
                retVo = null;
            } else {
                entity.setName(appTag.getName());
                entity.setDescription(appTag.getDescription());
                this.appTagDao.update(entity);
                retVo = appTag;
            }
        }
        return retVo;
    }

    @Override
    public List<ApplicationTagVo> getApplicationTag() {
        return this.appTagDao.retrive()
                .stream()
                .map(entity -> {
                    ApplicationTagVo vo = new ApplicationTagVo();
                    vo.setSid(entity.getSid());
                    vo.setName(entity.getName());
                    vo.setDescription(entity.getDescription());
                    vo.setUsage(entity.getAppUsage().getValue());
                    return vo;
                }).collect(Collectors.toList());
    }

    @Override
    public List<ApplicationTagVo> getApplicationTagByUsage(EnumAppUsage usage) {
        return this.appTagDao.retriveByUsage(usage.getValue())
                .stream()
                .map(entity -> {
                    ApplicationTagVo vo = new ApplicationTagVo();
                    vo.setSid(entity.getSid());
                    vo.setName(entity.getName());
                    vo.setDescription(entity.getDescription());
                    return vo;
                }).collect(Collectors.toList());
    }

    @Override
    public List<ApplicationTagVo> getApplicationTagWithVersionByUsage(EnumAppUsage usage) {
        List<Integer> tagIds = new ArrayList<Integer>();
        List<ApplicationTagVo> appTags = this.appTagDao
                .retriveByUsage(usage.getValue())
                .stream()
                .map(entity -> {
                    ApplicationTagVo vo = new ApplicationTagVo();
                    vo.setSid(entity.getSid());
                    vo.setName(entity.getName());
                    vo.setDescription(entity.getDescription());
                    tagIds.add(entity.getSid());
                    return vo;
                }).collect(Collectors.toList());
        Map<Integer, List<ApplicationVersionDo>> appVersionMap = this.appVersionDao
                .retriveByAppTagIds(tagIds)
                .stream()
                .collect(Collectors.groupingBy(ApplicationVersionDo::getApplicationTagId));
        appTags.forEach(appTag -> {
            if (appVersionMap.containsKey(appTag.getSid())) {
                appTag.setAppVersions(
                        appVersionMap.get(appTag.getSid())
                                     .stream()
                                     .map(entity -> {
                                         ApplicationVersionVo vo = new ApplicationVersionVo();
                                         vo.setSid(entity.getSid());
                                         vo.setVersion(entity.getVersion());
                                         vo.setDeployUrl(entity.getDeployUrl());
                                         vo.setStatus(entity.getStatus().getValue());
                                         vo.setCreateAt(entity.getCreateAt());
                                         vo.setUpdateAt(entity.getUpdateAt());
                                         return vo;
                                     }).collect(Collectors.toList()));
            }
        });
        return appTags;
    }

    @Override
    @Transactional(rollbackFor = { Exception.class })
    public ResultVo removeApplicationTagById(Integer id) {
        try {
            List<Integer> appVersionIds = 
                    this.appVersionDao.retriveByAppTagId(id)
                        .stream()
                        .map(appVersion -> {
                            return appVersion.getSid();
                        }).collect(Collectors.toList());
            this.appTagDao.delete(id);
            this.appVersionDao.deleteByAppTagId(id);
            this.appApiDao.deleteByAppVersionIds(appVersionIds);
            return new ResultVo();
        } catch (Exception e) {
            return new ResultVo(ResultEnums.FAILED, null, e.getMessage());
        }
    }

    @Override
    public ApplicationVersionVo saveApplicationVersion(ApplicationVersionVo appVersion) {
        ApplicationVersionVo retVo;
        if (null == appVersion.getSid()) {
            ApplicationVersionDo entity = new ApplicationVersionDo();
            entity.setVersion(appVersion.getVersion());
            entity.setDescription(appVersion.getDeployUrl());
            entity.setDeployUrl(appVersion.getDeployUrl());
            entity.setStatus(EnumVersionStatus.valueOf(appVersion.getStatus()));
            entity.setApplicationTagId(appVersion.getAppTag().getSid());
            this.appVersionDao.create(entity);
            appVersion.setSid(entity.getSid());
            retVo = appVersion;
        } else {
            ApplicationVersionDo entity = this.appVersionDao.retriveById(appVersion.getSid());
            if (null == entity) {
                retVo = null;
            } else {
                entity.setVersion(appVersion.getVersion());
                entity.setDescription(appVersion.getDescription());
                entity.setDeployUrl(appVersion.getDeployUrl());
                this.appVersionDao.update(entity);
                retVo = appVersion;
            }
        }
        return retVo;
    }

    @Override
    public List<ApplicationVersionVo> getApplicationVersionByAppTagId(Integer appTagId) {
        return this.appVersionDao.retriveByAppTagId(appTagId)
                    .stream()
                    .map(entity -> {
                        ApplicationVersionVo vo = new ApplicationVersionVo();
                        vo.setSid(entity.getSid());
                        vo.setVersion(entity.getVersion());
                        vo.setDescription(entity.getDescription());
                        vo.setDeployUrl(entity.getDeployUrl());
                        vo.setStatus(entity.getStatus().getValue());
                        vo.setCreateAt(entity.getCreateAt());
                        vo.setUpdateAt(entity.getUpdateAt());
                        return vo;
                    }).collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = { Exception.class })
    public ResultVo removeApplicationVersionById(Integer id) {
        try {
            this.appVersionDao.delete(id);
            this.appApiDao.deleteByAppVersionId(id);
            return new ResultVo();
        } catch (Exception e) {
            return new ResultVo(ResultEnums.FAILED, null, e.getMessage());
        }
    }

    @Override
    public ApplicationApiVo saveApplicationApi(ApplicationApiVo appApi) {
        ApplicationApiVo retVo;
        if (null == appApi.getSid()) {
            ApplicationApiDo entity = new ApplicationApiDo();
            entity.setTitle(appApi.getTitle());
            entity.setContent(appApi.getContent());
            entity.setUsage(EnumApiUsage.valueOf(appApi.getUsage()));
            entity.setApplicationVersionId(appApi.getAppVersion().getSid());
            this.appApiDao.create(entity);
            appApi.setSid(entity.getSid());
            retVo = appApi;
        } else {
            ApplicationApiDo entity = this.appApiDao.retriveById(appApi.getSid());
            if (null == entity) {
                retVo = null;
            } else {
                entity.setTitle(appApi.getTitle());
                entity.setContent(appApi.getContent());
                entity.setUsage(EnumApiUsage.valueOf(appApi.getUsage()));
                this.appApiDao.update(entity);
                retVo = appApi;
            }
        }
        return retVo;
    }

    @Override
    public PageModel getPageOfApplicationApiByAppVersionIdAndUsage(Integer appVersionId, 
                                                                   EnumApiUsage usage, 
                                                                   String fuzzyTitle,
                                                                   Integer pageNo, 
                                                                   Integer pageSize) {
        PageModel retPage = new PageModel();
        List<ApplicationApiVo> appApis = this.appApiDao
                .retriveByAppVersionIdAndPage(appVersionId, usage.getValue(), fuzzyTitle, (pageNo - 1) * pageSize, pageNo * pageSize)
                .stream()
                .map(entity -> {
                    ApplicationApiVo vo = new ApplicationApiVo();
                    vo.setSid(entity.getSid());
                    vo.setUsage(entity.getUsage().getValue());
                    vo.setTitle(entity.getTitle());
                    vo.setContent(entity.getContent());
                    return vo;
                }).collect(Collectors.toList());
        retPage.setObjects(appApis);
        Integer total = this.appApiDao.countByAppVersionId(appVersionId, usage.getValue(), fuzzyTitle);
        if (total % pageSize > 0) {
            retPage.setTotalPageNo(total / pageSize + 1);
        } else {
            retPage.setTotalPageNo(total / pageSize);
        }
        return retPage;
    }
    
    @Override
    public Integer getPageCountOfApplicationApiByAppVersionIdAndUsage(Integer appVersionId,
                                                                      EnumApiUsage usage,
                                                                      String fuzzyTitle,
                                                                      Integer pageSize) {
        Integer total = this.appApiDao.countByAppVersionId(appVersionId, usage.getValue(), fuzzyTitle);
        if (total % pageSize > 0) {
            return total / pageSize + 1;
        } else {
            return total / pageSize;
        }
    }

    @Override
    public ResultVo removeApplicationApiById(Integer id) {
        this.appApiDao.delete(id);
        return new ResultVo();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultVo deployByAppVersionId(Integer appVersionId) {
        ApplicationVersionDo appVersionDo = this.appVersionDao.retriveById(appVersionId);
        if (null == appVersionDo) {
            return new ResultVo(ResultEnums.FAILED, null, "The version of app is not found");
        }
        ApplicationTagDo appTagDo = this.appTagDao.retriveById(appVersionDo.getApplicationTagId());
        File appDeployLogFolder = new File(appDeployLog.getAppRecordDir());
        if (!appDeployLogFolder.exists()) {
            appDeployLogFolder.mkdirs();
        }
        File appDeployLogFile = new File(appDeployLogFolder, appTagDo.getName() + "-"
                + appVersionDo.getVersion() + "-" + LocalDateTime.now() + ".log");
        
        ProcessBuilder pb = new ProcessBuilder(appVersionDo.getDeployUrl());
        try {
            Process proc = pb.redirectOutput(appDeployLogFile).start();
            appDeployLog.getAppDeployProcMap()
                        .put(appVersionDo.getSid(), new AppDeployProcess(proc, appDeployLogFile, 0));
        } catch (IOException e) {
            dfaceLog.error(e);
            return new ResultVo(ResultEnums.FAILED, null, "IO exception of deploying");
        }
        
        return new ResultVo();
    }

    @Override
    public ResultVo getDeployLogByAppVersionId(Integer appVersionId) {
        AppDeployProcess proc = appDeployLog.getAppDeployProcMap().get(appVersionId);
        if (null == proc) {
            return new ResultVo(ResultEnums.FAILED, null, "The deployment log is not found");
        }
        AppDeployRecord record = new AppDeployRecord();
        record.setFinished(false);
        try (RandomAccessFile aFile = new RandomAccessFile(proc.getAppRecordFile(), "r")) {
            aFile.seek(proc.getPosition());
            long endPosition = Math.min(proc.getPosition() + appDeployLog.getReadLength(),
                    aFile.length());
            StringBuilder retStr = new StringBuilder();
            while (aFile.getFilePointer() < endPosition) {
                retStr.append(aFile.readLine() + "\n");
            }
            proc.setPosition(aFile.getFilePointer());
            record.setMessage(retStr.toString());
            if (!proc.getProc().isAlive() && aFile.getFilePointer() == aFile.length()) {
                appDeployLog.getAppDeployProcMap().remove(appVersionId);
                ApplicationVersionDo appVersionDo = appVersionDao.retriveById(appVersionId);
                appVersionDo.setStatus(EnumVersionStatus.PUBLISHED);
                appVersionDao.update(appVersionDo);
                record.setFinished(true);
            }
        } catch (IOException e) {
            return new ResultVo(ResultEnums.FAILED, null, "IO exception of reading log");
        }
        return new ResultVo(record);
    }

}
